# Chapter 11: Sound Programming
## Audio bank API
The Commander X16 provides many convenience routines for controlling the YM2151 and VERA PSG. These are called similarly to how KERNAL API calls are done in machine language.
In order to gain access to these routines, you must either use `jsrfar` from the KERNAL API:
```ASM
AUDIO_BANK = $0A
jsr jsrfar ; $FF6E
.word ym_init ; $C063
.byte AUDIO_BANK
```
or switch to ROM bank `$0A` directly:
```ASM
lda #$0A ; Audio bank number
sta $01 ; ROM bank register
```
Conveniently, the KERNAL API still exists in this bank, and calling a KERNAL API routine will automatically switch your ROM bank back to the KERNAL bank to perform the routine and then switch back right before returning, so there's usually no need for your audio-centric program to switch away from the audio bank to perform the occasional KERNAL API call.
### Audio API routines
For the audio chips, some of the documentation uses the words _channel_ and _voice_ interchangeably. This table of API routines uses _channel_ for the 8 on the YM2151, and _voice_ for the 16 on the PSG.
| Label | Address | Class | Description | Inputs | Returns | Preserves |
|-|-|-|-|-|-|-|
| `audio_init` | `$C09F` | - | Wrapper routine that calls both `psg_init` and `ym_init` followed by `ym_loaddefpatches`. This is the routine called by the KERNAL at reset. | none | none | none |
| `bas_fmchordstring` | `$C08D` | BASIC | Starts playing all of notes specified in a string. This uses the same parser as `bas_fmplaystring` but instead of playing the notes in sequence, it starts playback of each note in the string, on many channels as is necessary, then returns to the caller without delay. The first FM channel that is used is the one specified by calling `bas_playstringvoice` prior to calling this routine. The string pointer must point to low RAM (`$0000`-`$9EFF`). | .A = string length
.X .Y = pointer to string | none | none |
| `bas_fmfreq` | `$C000` | BASIC | Plays a note specified in Hz on an FM channel | .A = channel
.X .Y = 16-bit frequency in Hz
c clear = normal
c set = no retrigger | c clear = success
c set = error | none |
| `bas_fmnote` | `$C003` | BASIC | Plays a note specified in BASIC format on an FM channel | .A = channel
.X = note (BASIC format) .Y = fractional semitone
c clear = normal
c set = no retrigger | c clear = success
c set = error | none |
| `bas_fmplaystring` | `$C006` | BASIC | Plays a note script using the FM channel which was specified on a previous call to `bas_playstringvoice`. This string pointer must point to low RAM (`$0000`-`$9EFF`). This routine depends on interrupts being enabled. In particular, it uses `WAI` as a delay for timing, so it expects IRQ to be asserted and acknowledged once per video frame, which is the case by default on the system. Stops playback and returns control if the STOP key is pressed. | .A = string length
.X .Y = pointer to string | none | none |
| `bas_fmvib` | `$C009` | BASIC | Sets the LFO speed and both amplitude and frequency depth based on inputs. Also sets the LFO waveform to triangle. | .A = speed .X = PMD/AMD depth | c clear = success
c set = error | none |
| `bas_playstringvoice` | `$C00C` | BASIC | Preparatory routine for `bas_fmplaystring` and `bas_psgplaystring` to set the voice/channel number for playback | .A = PSG/YM voice/channel | none | .A .X |
| `bas_psgchordstring` | `$C090` | BASIC | Starts playing all of notes specified in a string. This uses the same parser as `bas_psgplaystring` but instead of playing the notes in sequence, it starts playback of each note in the string, on many voices as is necessary, then returns to the caller without delay. The first PSG voice that is used is the one specified by calling `bas_playstringvoice` prior to calling this routine. The string pointer must point to low RAM (`$0000`-`$9EFF`). | .A = string length
.X .Y = pointer to string | none | none |
| `bas_psgfreq` | `$C00F` | BASIC | Plays a note specified in Hz on a PSG voice | .A = voice
.X .Y = 16-bit frequency | c clear = success
c set = error | none |
| `bas_psgnote` | `$C012` | BASIC | Plays a note specified in BASIC format on a PSG voice | .A = voice
.X = note (BASIC format) .Y = fractional semitone | c clear = success
c set = error | none |
| `bas_psgwav` | `$C015` | BASIC | Sets a waveform and duty cycle for a PSG voice | .A = voice .X 0-63 = Pulse, 1/128 - 64/128 duty cycle
.X 64-127 = Sawtooth
.X 128-191 = Triangle
.X 192-255 = Noise | c clear = success
c set = error | none |
| `bas_psgplaystring` | `$C018` | BASIC | Plays a note script using the PSG voice which was specified on a previous call to `bas_playstringvoice`. This string pointer must point to low RAM (`$0000`-`$9EFF`). This routine depends on interrupts being enabled. In particular, it uses `WAI` as a delay for timing, so it expects IRQ to be asserted and acknowledged once per video frame, which is the case by default on the system. Stops playback and returns control if the STOP key is pressed. | .A = string length
.X .Y = pointer to string | none | none |
| `notecon_bas2fm` | `$C01B` | Conversion | Convert a note in BASIC format to a YM2151 KC code | .X = note (BASIC format) | .X = note (YM2151 KC)
c clear = success
c set = error | .Y |
| `notecon_bas2midi` | `$C01E` | Conversion | Convert a note in BASIC format to a MIDI note number | .X = note (BASIC format) | .X = MIDI note
c clear = success
c set = error | .Y |
| `notecon_bas2psg` | `$C021` | Conversion | Convert a note in BASIC format to a PSG frequency | .X = note (BASIC format)
.Y = fractional semitone | .X .Y = PSG frequency
c clear = success
c set = error | none |
| `notecon_fm2bas` | `$C024` | Conversion | Convert a note in YM2151 KC format to a note in BASIC format | .X = YM2151 KC | .X = note (BASIC format)
c clear = success
c set = error | .Y |
| `notecon_fm2midi` | `$C027` | Conversion | Convert a note in YM2151 KC format to a MIDI note number | .X = YM2151 KC | .X = MIDI note
c clear = success
c set = error | .Y |
| `notecon_fm2psg` | `$C02A` | Conversion | Convert a note in YM2151 KC format to a PSG frequency | .X = YM2151 KC
.Y = fractional semitone | .X .Y = PSG frequency
c clear = success
c set = error | none |
| `notecon_freq2bas` | `$C02D` | Conversion | Convert a frequency in Hz to a note in BASIC format and a fractional semitone | .X .Y = 16-bit frequency in Hz | .X = note (BASIC format)
.Y = fractional semitone
c clear = success
c set = error | none |
| `notecon_freq2fm` | `$C030` | Conversion | Convert a frequency in Hz to YM2151 KC and a fractional semitone (YM2151 KF) | .X .Y = 16-bit frequency in Hz | .X = YM2151 KC
.Y = fractional semitone (YM2151 KF)
c clear = success
c set = error | none |
| `notecon_freq2midi` | `$C033` | Conversion | Convert a frequency in Hz to a MIDI note and a fractional semitone | .X .Y = 16-bit frequency in Hz | .X = MIDI note
.Y = fractional semitone
c clear = success
c set = error | none |
| `notecon_freq2psg` | `$C036` | Conversion | Convert a frequency in Hz to a VERA PSG frequency | .X .Y = 16-bit frequency in Hz | .X .Y = 16-bit frequency in VERA PSG format
c clear = success
c set = error | none |
| `notecon_midi2bas` | `$C039` | Conversion | Convert a MIDI note to a note in BASIC format | .X = MIDI note | .X = note (BASIC format)
c clear = success
c set = error | .Y |
| `notecon_midi2fm` | `$C03C` | Conversion | Convert a MIDI note to a YM2151 KC. Fractional semitone is unneeded as it is identical to KF already. | .X = MIDI note. | .X = YM2151 KC
c clear = success
c set = error | .Y |
| `notecon_midi2psg` | `$C03F` | Conversion | Convert a MIDI note and fractional semitone to a PSG frequency | .X = MIDI note
.Y = fractional semitone | .X .Y = 16-bit frequency in VERA PSG format
c clear = success
c set = error | none |
| `notecon_psg2bas` | `$C042` | Conversion | Convert a frequency in VERA PSG format to a note in BASIC format and a fractional semitone | .X .Y = 16-bit frequency in VERA PSG format | .X = note (BASIC format)
.Y = fractional semitone
c clear = success
c set = error | none |
| `notecon_psg2fm` | `$C045` | Conversion | Convert a frequency in VERA PSG format to YM2151 KC and a fractional semitone (YM2151 KF) | .X .Y = 16-bit frequency in VERA PSG format | .X = YM2151 KC
.Y = fractional semitone (YM2151 KF)
c clear = success
c set = error | none |
| `notecon_psg2midi` | `$C048` | Conversion | Convert a frequency in VERA PSG format to a MIDI note and a fractional semitone | .X .Y = 16-bit frequency in VERA PSG format | .X = MIDI note
.Y = fractional semitone
c clear = success
c set = error | none |
| `psg_getatten` | `$C093` | VERA PSG | Retrieve the attenuation value for a voice previously set by `psg_setatten` | .A = voice | .X = attenuation value | .A |
| `psg_getpan` | `$C096` | VERA PSG | Retrieve the simple panning value that is currently set for a voice. | .A = voice | .X = pan value | .A |
| `psg_init` | `$C04B` | VERA PSG | Initialize the state of the PSG. Silence all voices. Reset the attenuation levels to 0. Set "playstring" defaults including `O4`, `T120`, `S1`, and `L4`. Set all PSG voices to the pulse waveform at 50% duty with panning set to both L+R | none | none | none |
| `psg_playfreq` | `$C04E` | VERA PSG | Turn on a PSG voice at full volume (factoring in attenuation) and set its frequency | .A = voice
.X .Y = 16 bit frequency in VERA PSG format | none | none |
| `psg_read` | `$C051` | VERA PSG | Read a value from one of the VERA PSG registers. If the selected register is a volume register, return either the cooked value (attenuation applied) or the raw value (as received by `psg_write` or `psg_setvol`, or as set by `psg_playfreq`) depending on the state of the carry flag | .X = PSG register address (offset from `$1F9C0`)
c clear = if volume, return raw
c set = if volume, return cooked | .A = register value | .X |
| `psg_setatten` | `$C054` | VERA PSG | Set the attenuation value for a PSG voice. The valid range is from `$00` (full volume) to `$3F` (fully muted). API routines which affect volume will deduct the attenuation value from the intended volume before setting it. Calls to this routine while a note is playing will change the output volume of the voice immediately. This control can be considered a "master volume" for the voice. | .A = voice
.X = attenuation | none | none |
| `psg_setfreq` | `$C057` | VERA PSG | Set the frequency of a PSG voice without changing any other attributes of the voice | .A = voice
.X .Y = 16 bit frequency in VERA PSG format | none | none |
| `psg_setpan` | `$C05A` | VERA PSG | Set the simple panning for the voice. A value of `0` will silence the voice entirely until another pan value is set. | .A = voice
.X 0 = none
.X 1 = left
.X 2 = right
.X 3 = both | none | none |
| `psg_setvol` | `$C05D` | VERA PSG | Set the volume for the voice. The volume that's written to the VERA has attenuation applied. Valid volumes range from `$00` to `$3F` inclusive | .A = voice
.X = volume | none | none |
| `psg_write` | `$C060` | VERA PSG | Write a value to one of the VERA PSG registers. If the selected register is a volume register, attenuation will be applied before the value is written to the VERA | .A = value
.X = PSG register address (offset from `$1F9C0`) | none | .A .X |
| `psg_write_fast` | `$C0A2` | VERA PSG | Same effect as `psg_write` but does not preserve the state of the VERA CTRL and ADDR registers. It also assumes VERA_CTRL bit 0 is clear, VERA_ADDR0_H = $01 (auto increment 0 recommended), and VERA_ADDR0_M = $F9. This routine is meant for use by sound engines that typically write out multiple PSG registers in a loop. | .A = value
.X = PSG register address (offset from `$1F9C0`) | none | .A .X |
| `ym_getatten` | `$C099` | YM2151 | Retrieve the attenuation value for a channel previously set by `ym_setatten` | .A = channel | .X = attenuation value | .A |
| `ym_getpan` | `$C09C` | YM2151 | Retrieve the simple panning value that is currently set for a channel. | .A = channel | .X = pan value | .A |
| `ym_init` | `$C063` | YM2151 | Initialize the state of the YM chip. Silence all channels by setting the release part of the ADSR envelope to max and then setting all channels to released. Reset all attenuation levels to 0. Set "playstring" defaults including `O4`, `T120`, `S1`, and `L4`. Set panning for all channels set to both L+R. Reset LFO state. Set all of the other registers to `$00` | none | c clear = success
c set = error | none |
| `ym_loaddefpatches` | `$C066` | YM2151 | Load a default set of patches into the 8 channels.
`C0: Piano (0)`
`C1: E. Piano (5)`
`C2: Vibraphone (11)`
`C3: Fretless (35)`
`C4: Violin (40)`
`C5: Trumpet (56)`
`C6: Blown Bottle (76)`
`C7: Fantasia (88)` | none | c clear = success
c set = error | none |
| `ym_loadpatch` | `$C069` | YM2151 | Load into a channel a patch preset by number (0-161) from the audio bank, or from an arbitrary memory location. High RAM addresses (`$A000`-`$BFFF`) are accepted in this mode. | .A = channel
c clear = .X .Y = patch address
c set = .X = patch number | c clear = success
c set = error | none |
| `ym_loadpatchlfn` | `$C06C` | YM2151 | Load patch into a channel by way of an open logical file number. This routine will read 26 bytes from the open file, or possibly fewer bytes if there's an error condition. The routine will leave the file open on return. On return if c is set, check .A for the error code. | .A = channel
.X = Logical File Number | c clear = success
c set .A=0 = YM error
c set .A&3=2 = read timeout
c set .A&3=3 = file not open
c set .A&64=64 = EOF
c set .A&128=128 = device not present | none |
| `ym_playdrum` | `$C06F` | YM2151 | Load a patch associated with a MIDI drum note number and trigger it on a channel. Valid drum note numbers mirror the General MIDI percussion standard and range from 25 (Snare Roll) through 87 (Open Surdo). Note 0 will release the note. After the drum is played, the channel will still contain the patch for the drum sound and thus may not sound musical if you attempt to play notes on it before loading another instrument patch. | .A = channel
.X = drum note | c clear = success
c set = error | none |
| `ym_playnote` | `$C072` | YM2151 | Set a KC/KF on a channel and optionally trigger it. | .A = channel
.X = KC
.Y = KF (fractional semitone)
c clear = trigger
c set = no trigger | c clear = success
c set = error | none |
| `ym_setatten` | `$C075` | YM2151 | Set the attenuation value for a channel. The valid range is from `$00` (full volume) to `$7F` (fully muted). API routines which affect TL or CON will add the attenuation value to the intended TL on operators that are carriers before setting it. Calls to this routine will change the TL of the channel's carriers immediately. This control can be considered a "master volume" for the channel. | .A = channel
.X = attenuation | c clear = success
c set = error | .A .X |
| `ym_setdrum` | `$C078` | YM2151 | Load a patch associated with a MIDI drum note number and set the KC/KF for it on a channel. Called by `ym_playdrum`. | .A = channel
.X = drum note | c clear = success
c set = error | none |
| `ym_setnote` | `$C07B` | YM2151 | Set a KC/KF on a channel. Called by `ym_playnote`. | .A = channel
.X = KC
.Y = KF (fractional semitone) | c clear = success
c set = error | none |
| `ym_setpan` | `$C07E` | YM2151 | Set the simple panning for the channel. A value of `0` will silence the channel entirely until another pan value is set. | .A = channel
.X 0 = none
.X 1 = left
.X 2 = right
.X 3 = both | c clear = success
c set = error | none |
| `ym_read` | `$C081` | YM2151 | Read a value from the in-RAM shadow of one of the YM2151 registers. The YM2151's internal registers cannot be read from, but this API keeps state of what was written, so this routine will be able to retrieve chip values for you. If the selected register is a TL register, return either the cooked value (attenuation applied) or the raw value (as received by `ym_write`) depending on the state of the carry flag | .X = YM2151 register address
c clear = if TL, return raw
c set = if TL, return cooked | .A = register value
c clear = success
c set = error | .X |
| `ym_release` | `$C084` | YM2151 | Release a note on a channel. If a note is not playing, this routine has no tangible effect | .A = channel | c clear = success
c set = error | none |
| `ym_trigger` | `$C087` | YM2151 | Trigger the currently configured note on a channel, optionally releasing the channel first depending on the state of the carry flag. | .A = channel
c clear = release first
c set = no release | c clear = success
c set = error | none |
| `ym_write` | `$C08A` | YM2151 | Write a value to one of the YM2151 registers and to the in-RAM shadow copy. If the selected register is a TL register, attenuation will be applied before the value is written. Writes which affect which operators are carriers will have TL values for that channel appropriately recalculated and rewritten | .A = value
.X = YM register address | c clear = success
c set = error | .A .X |
## A note on semitones (get it?)
It may be advantageous to consider storing note data internally as the MIDI
representation with a fractional component if you want things like pitch slides
to behave the same way between the PSG and YM2151.
Essentially, it can be thought of as an 8.8 fixed point 16-bit number.
The YM2151 handles semitones differently than the PSG and requires converting
the MIDI note to the appropriate KC value using `notecon_midi2fm`. KF is the
fractional semitone (albeit with only the top 6-bits used)
and requires no conversion.
The PSG, by contrast, operates with linear pitch which is why
`notecon_midi2psg` also takes the fractional component (y) as input.
Thus, if you manage all your pitch slides using MIDI notes along with a fractional
component, you can then convert this directly over to PSG or YM2151 as required
and end up with the same pitch (or close enough to it).
## Direct communication with the YM2151 and VERA PSG vs API
Use of the API routines above is not required to access the capabilities of the sound chips. However, mixing raw writes to a chip and API access for the same chip is not recommended, particularly where PSG volumes and YM2151 TL and RLFBCON registers are concerned. The API processes volumes, calculating attenuation and adjusting the output volume accordingly, and the API will be oblivious to direct manipulation of the sound chips.
The sections below describe how to do raw access to the sound chips outside of the API.
## VERA PSG and PCM Programming
* For VERA PSG and PCM, refer to [Chapter 9](X16%20Reference%20-%2009%20-%20VERA%20Programmer's%20Reference.md#chapter-9-vera-programmers-reference).
## YM2151 (OPM) FM Synthesis
The Yamaha YM2151 (OPM) sound chip is an FM synthesizer ASIC in the Commander X16.
It is connected to the system bus at I/O address `0x9F40` (address register) and at `0x9F41` (data register). It has 8 independent voices with 4 FM operators each. Each
voice is capable of left/right/both audio channel output. The four operators of each channel may be connected in one of 8 pre-defined "connection algorithms" in order to produce a wide variety of timbres.
### YM2151 Communication
There are 3 basic operations to communicate with the YM chip: Reading its status,
address select, and data write. These are performed by reading from or writing to
one of the two I/O addresses as follows:
Address|Name|Read Action|Write Action
--|--|-----|-----
0x9F40|`YM_address`|Undefined (returns ?)|Selects the internal register address where data is written.
0x9F41|`YM_data`|Returns the `YM_status` byte|Writes the value into the currently-selected internal address.
The values stored in the YM's internal registers are write-only. If you need
to know the values in the registers, you must store a copy of the values somewhere in memory as you write updates to the YM.
### YM Write Procedure
1. Ensure YM is not busy (see Write Timing below).
2. Select the desired internal register address by writing it into `YM_address`.
3. Write the new value for this register into `YM_data`.
*Note:* You may write into the same register multiple times without repeating a write to `YM_address`. The same register will be updated with each data write.
### Write Timing
**The YM2151 is sensitive to the speed at which you write data into it. If you
make writes when it is not ready to receive them, they will be dropped and the sound output will be corrupted.**
You must include a delay between writes to the address select register ($9F40) and the subsequent data write. 10 CPU cycles is the recommended minimum delay.
The YM becomes `BUSY` for approximately 150 CPU cycles' (at 8Mhz) whenever it receives a data write. *Any writes into YM_data during this `BUSY` period will be ignored!*
In order to avoid this, you can use the `BUSY` flag which is bit 7 of the `YM status` byte. Read the status byte from `YM_data` ($9F41). If the top bit (7) is set, the YM may not be written into at this time. Note that it is not _required_ that you read `YM_status`, only that writes occur no less than ~150 CPU cycles apart. For instance, BASIC executes slowly enough that you are in no danger of writing into the YM too quickly, so BASIC programs may skip checking `YM_status`.
Lastly, the `BUSY` flag sometimes takes a (very) short period before it goes high. This has only been observed when IMMEDIATELY polling the flag after a write into `YM_data.` As long as your code does not do so, this quirk should not be an issue.
### Example Code
**Assembly Language:**
```ASM
check_busy:
BIT YM_data ; check busy flag
BMI check_busy ; wait until busy flag is clear
LDA #$08 ; Select YM register $08 (Key-Off/On)
STA YM_addr ;
NOP ;<-+
NOP ; |
NOP ; +--slight pause before writing data
NOP ; |
NOP ;<-+
LDA #$04 ; Write $04 (Release note on channel 4).
STA YM_data
RTS
```
**BASIC:**
```BASIC
10 YA=$9F40 : REM YM_ADDRESS
20 YD=$9F41 : REM YM_DATA
30 POKE YA,$29 : REM CHANNEL 1 NOTE SELECT
40 POKE YD,$4A : REM SET NOTE = CONCERT A
50 POKE YA,$08 : REM SELECT THE KEY ON/OFF REGISTER
60 POKE YD,$00+1 : REM RELEASE ANY NOTE ALREADY PLAYING ON CHANNEL 1
70 POKE YD,$78+1 : REM KEY-ON VOICE 1 TO PLAY THE NOTE
80 FOR I=1 TO 100 : NEXT I : REM DELAY WHILE NOTE PLAYS
90 POKE YD,$00+1 : REM RELEASE THE NOTE
```
## YM2151 Internal Addressing
The YM register address space can be thought of as being divided into 3 ranges:
Range|Type|Description
--|---|----
00 .. 1F|Global Values|Affect individual global parameters such as LFO frequency, noise enable, etc.
20 .. 3F|Channel CFG|Parameters in groups of 8, one per channel. These affect the whole channel.
40 .. FF|Operator CFG|Parameters in groups of 32 - these map to individual operators of each voice.
## YM2151 Register Map
### Global Settings
| Addr | Register | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Description |
|---|---|---|---|---|---|---|---|---|---|---|
| $01 | Test Register | ! | ! | ! | ! | ! | ! | LR | ! |
Bit 1 is the LFO reset bit. Setting it disables the LFO and holds the oscillator at 0. Clearing it enables the LFO. All other bits control various test functions and should not be written into. |
| $08 | Key Control | . | C2 | M2 | C1 | M1 | CHA |
Starts and Releases notes on the 8 channels. Setting/Clearing bits for M1,C1,M2,C2 controls the key state for those operators on channel CHA. NOTE: The operator order is different than the order they appear in the Operator configuration registers! |
||
| $0F | Noise Control | NE | . | . | NFRQ |
NE = Noise Enable NFRQ = Noise Frequency When eabled, C2 of channel 7 will use a noise waveform instead of a sine waveform. |
||||
| $10 | Ta High | CLKA1 | Top 8 bits of Timer A period setting | |||||||
| $11 | Ta Low | . | . | . | . | . | . | CLKA2 | Bottom 2 bits of Timer A period setting | |
| $12 | Timer B | CLKB | Timer B period setting | |||||||
| $14 | IRQ Control | CSM | . | Clock ACK | IRQ EN | Clock Start |
CSM: When a timer expires, trigger note key-on for all channels. For the other 3 fields, lower bit = Timer A, upper bit = Timer B. Clock ACK: clears the timer's bit in the YM_status byte and acknowledges the IRQ. |
|||
| $18 | LFO Freq. | LFRQ | Sets LFO frequency. $00 = ~0.008Hz $FF = ~32.6Hz |
|||||||
| $19 | LFO Amplitude | 0 | AMD |
AMD = Amplitude Modulation Depth PMD = Phase Modulation (vibrato) Depth Bit 7 determines which parameter is being set when writing into this register. |
||||||
| 1 | PMD | |||||||||
| $1B | CT / LFO Waveform | CT | . | . | . | . | W |
CT: sets output pins CT1 and CT1 high or low. (not connected to anything in X16) W: LFO Waveform: 0-4 = Saw, Square, Triangle, Noise For sawtooth: PM->//// AM->\\\\ |
||
| Register Range | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Description |
|---|---|---|---|---|---|---|---|---|---|
| $20 + channel | RL | FB | CON |
|
|||||
| $28 + channel | . | KC | |||||||
| $30 + channel | KF | . | . | ||||||
| $38 + channel | . | PMS | . | . | AMS | ||||
| Register Range | Operator | Bit 7 | Bit 6 | Bit 5 | Bit 4 | Bit 3 | Bit 2 | Bit 1 | Bit 0 | Description |
|---|---|---|---|---|---|---|---|---|---|---|
| $40 | M1: $40+channel | . | DT1 | MUL |
|
|||||
| M2: $48+channel | ||||||||||
| C1: $50+channel | ||||||||||
| C2: $58+channel | ||||||||||
| $60 | M1: $60+channel | . | TL |
|
||||||
| M2: $68+channel | ||||||||||
| C1: $70+channel | ||||||||||
| C2: $78+channel | ||||||||||
| $80 | M1: $80+channel | KS | . | AR |
|
|||||
| M2: $88+channel | ||||||||||
| C1: $90+channel | ||||||||||
| C2: $98+channel | ||||||||||
| $A0 | M1: $A0+channel | A M E n a |
. | . | D1R |
|
||||
| M2: $A8+channel | ||||||||||
| C1: $B0+channel | ||||||||||
| C2: $B8+channel | ||||||||||
| $C0 | M1: $C0+channel | DT2 | . | D2R |
|
|||||
| M2: $C8+channel | ||||||||||
| C1: $D0+channel | ||||||||||
| C2: $D8+channel | ||||||||||
| $E0 | M1: $E0+channel | D1L | RR |
|
||||||
| M2: $E8+channel | ||||||||||
| C1: $F0+channel | ||||||||||
| C2: $F8+channel | ||||||||||
| 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
| - | C2 | M2 | C1 | M1 | Channel | ||